package config

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/sunny-zhangqiang/zcache_new/logger"
	"gopkg.in/yaml.v3"
	"os"
)

type MemConfig struct {
	ListNum           int `yaml:"ListNum"`
	OneListCacheItems int `yaml:"OneListCacheItems"`
	OneListCacheSize  int `yaml:"OneListCacheSize"`
}

type DiskConfig struct {
	FileName   string `yaml:"FileName"`
	FileSizeMB int64  `yaml:"FileSizeMB"`
	Weight     int    `yaml:"Weight"`
}

type KickVolumeConfig struct {
	MaxErrors     int `yaml:"MaxErrors"`
	ErrTimeoutSec int `yaml:"ErrTimeoutSec"`
}

type CacheConfig struct {
	MemCache MemConfig     `yaml:"MemCache"`
	SSDCache []*DiskConfig `yaml:"SSDCache"` // SSD缓存介质列表, 可以为空
	HDDCache []*DiskConfig `yaml:"HDDCache"` // HDD缓存介质列表, 不可为空

	MoveGoroutineNum      int  `yaml:"MoveGoroutineNum"`      // Move协程数量
	VolumeNum             int  `yaml:"VolumeNum"`             // 每个磁盘划分多少个分区
	FileAvgSizeKB         int  `yaml:"FileAvgSizeKB"`         // 平均文件大小, 用于计算索引个数
	BlockSizeMB           int  `yaml:"BlockSizeMB"`           // 块大小, 8MB的整数倍.
	IndexFlushDirtyNum    int  `yaml:"IndexFlushDirtyNum"`    // 多少个脏数据时才将索引数据落盘
	IndexFlushIntervalSec int  `yaml:"IndexFlushIntervalSec"` // 多久尝试刷新一次索引
	IndexFlushStepSec     int  `yaml:"IndexFlushStepSec"`     // 索引每一步刷新间隔
	MBlockPoolCapacity    int  `yaml:"MBlockPoolCapacity"`    // 每个volume持有多少个MBlock
	BlockRecycleThreshold int  `yaml:"BlockRecycleThreshold"` // 空闲空间不足 2 * BlockRecycleThreshold时开始回收空间
	HotspotWindowNum      int  `yaml:"HotspotWindowNum"`      // 对最后的几个Block进行热点统计
	HotSpotRatio          int  `yaml:"HotSpotRatio"`          // 热点对象百分比, 取值为0-50
	EnableTimerFlush      bool `yaml:"EnableTimerFlush"`      // 是否开启定期刷新
	TimerFlushIntervalSec int  `yaml:"TimerFlushIntervalSec"` // 定期刷新周期

	KickVolume KickVolumeConfig `yaml:"KickVolume"`
}

func FixCacheConfig(cfg *CacheConfig) error {
	if cfg == nil {
		return errors.New("invalid cache config")
	}

	if cfg.SSDCache != nil {
		for _, v := range cfg.SSDCache {
			if v.FileName == "" {
				return errors.New("invalid cfg.SSDCache.Item.FileName")
			}

			if v.Weight == 0 {
				v.Weight = 10
			}
		}
	}

	if cfg.HDDCache == nil {
		return errors.New("invalid cfg.HDDCache")
	}

	for _, v := range cfg.HDDCache {
		if v.FileName == "" {
			return errors.New("invalid cfg.HDDCache.Item.FileName")
		}

		if v.Weight == 0 {
			v.Weight = 10
		}
	}

	if cfg.MoveGoroutineNum <= 0 {
		cfg.MoveGoroutineNum = 2
	}

	if cfg.VolumeNum <= 0 {
		cfg.VolumeNum = 1
	}

	if cfg.FileAvgSizeKB == 0 {
		return errors.New("please configure reasonable cfg.FileAvgSizeKB")
	}

	if cfg.FileAvgSizeKB < 4 {
		cfg.FileAvgSizeKB = 4
	}

	// default 60s
	if cfg.IndexFlushIntervalSec <= 0 {
		cfg.IndexFlushIntervalSec = 60
	}

	if cfg.IndexFlushStepSec <= 0 {
		cfg.IndexFlushStepSec = 1
	}

	if cfg.MBlockPoolCapacity <= 0 {
		cfg.MBlockPoolCapacity = 4
	}

	if cfg.BlockRecycleThreshold <= 0 {
		cfg.BlockRecycleThreshold = 8
	}

	if cfg.HotspotWindowNum <= 0 {
		cfg.HotspotWindowNum = 32
	}

	if cfg.HotSpotRatio < 0 || cfg.HotSpotRatio > 50 {
		return errors.New("invalid cfg.HotSpotRatio, only values between 0-50 are allowed")
	}

	// default 15s
	if cfg.TimerFlushIntervalSec <= 0 {
		cfg.TimerFlushIntervalSec = 15
	}

	return nil
}

type Server struct {
	ServerName   string `yaml:"ServerName"`
	LockFilePath string `yaml:"LockFilePath"` // 锁文件路径

	ProbListen    string   `yaml:"ProbListen"`    // TCP探测端口
	ZTPListeners  []string `yaml:"ZTPListeners"`  // ZTP监听地址
	HttpListeners []string `yaml:"HttpListeners"` // Http监听器地址

	QuitTimeoutSec            int64 `yaml:"QuitTimeoutSec"`            // 平滑退出超时
	ProbTimeoutMSec           int64 `yaml:"ProbTimeoutMSec"`           // Prob超时时间
	HttpIdleTimeoutMSec       int64 `yaml:"HttpIdleTimeoutMSec"`       // http连接的最大空闲时间
	HttpReadHeaderTimeoutMSec int64 `yaml:"HttpReadHeaderTimeoutMSec"` // 读取请求头的最长时间

	ZTPIdleTimeoutMSec  int64 `yaml:"ZTPIdleTimeoutMSec"`  // ZTP连接空闲时间
	ZTPReadTimeoutMSec  int64 `yaml:"ZTPReadTimeoutMSec"`  // ZTP读超时
	ZTPWriteTimeoutMSec int64 `yaml:"ZTPWriteTimeoutMSec"` // ZTP写超时

	MergeTimeoutMSec      int64 `yaml:"MergeTimeoutMSec"`      // merge node超时定时器
	ChunkMergeTimeoutMSec int64 `yaml:"ChunkMergeTimeoutMSec"` // chunk merge node超时定时器

	ActiveFile string `yaml:"ActiveFile"` // 文件存在时表示当前服务处于上线状态

	EnableErrLog           bool   `yaml:"EnableErrLog"`           // 开启error log
	ErrLogLevel            string `yaml:"ErrLogLevel"`            //
	ErrLogPath             string `yaml:"ErrLogPath"`             // 必须设置绝对路径
	ErrLogScrollingMode    string `yaml:"ErrLogScrollingMode"`    // 日志滚动模式, none, minute, hour, day
	EnableAccessLog        bool   `yaml:"EnableAccessLog"`        //
	AccessLogPath          string `yaml:"AccessLogPath"`          // 必须设置绝对路径
	AccessLogScrollingMode string `yaml:"AccessLogScrollingMode"` // 日志滚动模式

	Cache *CacheConfig `yaml:"Cache"`
}

func CheckAndFixServerConf(sc *Server) error {
	if sc == nil {
		return errors.New("invalid server config")
	}

	if sc.ServerName == "" {
		return errors.New("please specify servername")
	}

	if sc.LockFilePath == "" {
		return errors.New("please specify lockfile path")
	}

	if sc.ProbListen == "" {
		return errors.New("please specify prob listen address")
	}

	if len(sc.ZTPListeners) == 0 {
		return errors.New("please specify ztp listeners")
	}

	if len(sc.HttpListeners) == 0 {
		return errors.New("please specify http listeners")
	}

	if sc.QuitTimeoutSec == 0 {
		sc.QuitTimeoutSec = 600
	}

	if sc.ProbTimeoutMSec == 0 {
		sc.ProbTimeoutMSec = 3 * 1000
	}

	if sc.HttpIdleTimeoutMSec == 0 {
		sc.HttpIdleTimeoutMSec = 15 * 1000
	}

	if sc.HttpReadHeaderTimeoutMSec == 0 {
		sc.HttpReadHeaderTimeoutMSec = 1 * 1000
	}

	if sc.ZTPIdleTimeoutMSec == 0 {
		sc.ZTPIdleTimeoutMSec = 15 * 1000
	}

	if sc.ZTPReadTimeoutMSec == 0 {
		sc.ZTPReadTimeoutMSec = 3 * 1000
	}

	if sc.ZTPWriteTimeoutMSec == 0 {
		sc.ZTPWriteTimeoutMSec = 3 * 1000
	}

	if sc.MergeTimeoutMSec == 0 {
		sc.MergeTimeoutMSec = 5 * 1000
	}

	if sc.ChunkMergeTimeoutMSec == 0 {
		sc.ChunkMergeTimeoutMSec = 5 * 1000
	}

	if sc.ActiveFile == "" || sc.ActiveFile[0] != '/' {
		return errors.New("active file must be an absolute path")
	}

	if sc.EnableErrLog {
		if sc.ErrLogLevel == "" {
			sc.ErrLogLevel = "error"
		}

		if sc.ErrLogScrollingMode == "" {
			sc.ErrLogScrollingMode = "none"
		}

		if sc.ErrLogLevel != "debug" &&
			sc.ErrLogLevel != "warn" &&
			sc.ErrLogLevel != "error" {
			return errors.New("invalid error log level")
		}

		if sc.ErrLogPath == "" || sc.ErrLogPath[0] != '/' {
			return errors.New("log path must be an absolute path ")
		}

		if sc.ErrLogScrollingMode != "none" &&
			sc.ErrLogScrollingMode != "minute" &&
			sc.ErrLogScrollingMode != "hour" &&
			sc.ErrLogScrollingMode != "day" {
			return errors.New("invalid error log scrolling mode")
		}
	}

	if sc.EnableAccessLog {
		if sc.AccessLogPath == "" || sc.AccessLogPath[0] != '/' {
			return errors.New("access log path must be an absolute path")
		}

		if sc.AccessLogScrollingMode != "none" &&
			sc.AccessLogScrollingMode != "minute" &&
			sc.AccessLogScrollingMode != "hour" &&
			sc.AccessLogScrollingMode != "day" {
			return errors.New("invalid access log scrolling mode")
		}
	}

	err := FixCacheConfig(sc.Cache)
	if err != nil {
		return err
	}

	return nil
}

var ServerConf *Server

func LoadServerConf(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return fmt.Errorf("load server conf failed, err: %v", err)
	}

	conf := new(Server)
	err = yaml.Unmarshal(data, conf)
	if err != nil {
		return fmt.Errorf("decode server conf failed, err: %v", err)
	}

	err = CheckAndFixServerConf(conf)
	if err != nil {
		return fmt.Errorf("check and fix server config failed, err: %v", err)
	}

	bs, _ := json.Marshal(conf)
	var out bytes.Buffer
	json.Indent(&out, bs, "", "\t")
	logger.SysDebug(out.String())

	ServerConf = conf
	return nil
}

// for testing
func InitTestingServerConf() {
	ServerConf = &Server{
		ServerName:    "testing-1",
		LockFilePath:  "/tmp/lockfile",
		ProbListen:    "127.0.0.1:9110",
		HttpListeners: []string{"127.0.0.1:9111"},
		ActiveFile:    "/tmp/active",
		Cache: &CacheConfig{
			HDDCache: []*DiskConfig{
				&DiskConfig{FileName: "/tmp/1", FileSizeMB: 1024, Weight: 1},
			},
			FileAvgSizeKB: 4,
		},
	}
	err := CheckAndFixServerConf(ServerConf)
	if err != nil {
		panic(err)
	}
}
